package com.lvhr.rpc.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.watch.WatchMonitor;
import cn.hutool.core.io.watch.Watcher;
import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.dialect.Props;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class ConfigLoader {
    private static final String[] EXTENSIONS = {".yml", ".yaml", ".properties"};
    private static final Map<String, Object> configCache = new ConcurrentHashMap<>();
    private static final Map<String, WatchMonitor> monitors = new ConcurrentHashMap<>();
    private static final Map<Class<?>, List<Runnable>> listeners = new ConcurrentHashMap<>();

    public static <T> T loadConfig(Class<T> tClass, String prefix) {
        return loadConfig(tClass, prefix, "");
    }

    public static synchronized <T> T loadConfig(Class<T> tClass, String prefix, String env) {
        String cacheKey = buildCacheKey(tClass, env);
        T config = (T) configCache.get(cacheKey);

        if (config == null) {
            String configPath = resolveConfigPath(env);
            Properties props = loadProperties(configPath);
            config = new Props(props).toBean(tClass, prefix);
            configCache.put(cacheKey, config);
            watchConfigChange(configPath, tClass, prefix, env);
        }
        return config;
    }

    private static String resolveConfigPath(String env) {
        String baseName = "application" + (StrUtil.isNotBlank(env) ? "-" + env : "");
        for (String ext : EXTENSIONS) {
            String filename = baseName + ext;
            if (FileUtil.exist(filename)) {
                return filename;
            }
        }
        throw new RuntimeException("Config file not found");
    }

    private static Properties loadProperties(String filename) {
        try {
            if (filename.endsWith(".properties")) {
                return loadPropFile(filename);
            } else {
                return loadYamlFile(filename);
            }
        } catch (Exception e) {
            throw new RuntimeException("Load config failed", e);
        }
    }

    private static Properties loadPropFile(String filename) throws Exception {
        Properties props = new Properties();
        props.load(FileUtil.getReader(filename, StandardCharsets.UTF_8));
        return props;
    }

    private static Properties loadYamlFile(String filename) throws Exception {
        Yaml yaml = new Yaml();
        Map<String, Object> yamlMap = yaml.load(FileUtil.getInputStream(filename));
        Properties props = new Properties();
        flattenYaml("", yamlMap, props);
        return props;
    }

    private static void flattenYaml(String prefix, Map<String, Object> map, Properties props) {
        map.forEach((key, value) -> {
            String fullKey = prefix.isEmpty() ? key : prefix + "." + key;
            if (value instanceof Map) {
                flattenYaml(fullKey, (Map<String, Object>) value, props);
            } else {
                props.put(fullKey, value.toString());
            }
        });
    }

    private static <T> void watchConfigChange(String path, Class<T> tClass,
                                              String prefix, String env) {
        if (monitors.containsKey(path)) return;

        WatchMonitor monitor = WatchMonitor.createAll(new File(path), new Watcher() {
            @Override
            public void onCreate(WatchEvent<?> event, Path currentPath) {
                handleChange();
            }

            @Override
            public void onModify(WatchEvent<?> event, Path currentPath) {
                handleChange();
            }

            @Override
            public void onDelete(WatchEvent<?> watchEvent, Path path) {
                handleChange();
            }

            @Override
            public void onOverflow(WatchEvent<?> watchEvent, Path path) {
                handleChange();
            }

            private void handleChange() {
                try {
                    Properties newProps = loadProperties(path);
                    T newBean = new Props(newProps).toBean(tClass, prefix);
                    String cacheKey = buildCacheKey(tClass, env);
                    configCache.put(cacheKey, newBean);
                    notifyListeners(tClass);
                } catch (Exception e) {
                    System.err.println("Hot update failed: " + e.getMessage());
                }
            }
        });
        monitor.start();
        monitors.put(path, monitor);
    }

    public static <T> void addListener(Class<T> tClass, Runnable callback) {
        listeners.computeIfAbsent(tClass, k -> new CopyOnWriteArrayList<>())
                .add(callback);
    }

    private static void notifyListeners(Class<?> tClass) {
        List<Runnable> callbacks = listeners.get(tClass);
        if (callbacks != null) {
            callbacks.forEach(Runnable::run);
        }
    }

    private static String buildCacheKey(Class<?> tClass, String env) {
        return tClass.getName() + "|" + env;
    }
}
